import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.BitSet;


public class Message {

    private int id;
    private byte[] payload;
    private BlockInfo block;

    private Message (int id, byte[] payload, byte[] block) {
        this.id = id;
        this.payload = payload;
        if (block == null)
            return;
        assert(block.length==8 || block.length==12);
        int index = ToolKit.bigEndianBytesToInt(block, 0);
        int offset = ToolKit.bigEndianBytesToInt(block, 4);
        int length;
        if (block.length == 12)
            length = ToolKit.bigEndianBytesToInt(block, 8);
        else
            length = payload.length;
        this.block = new BlockInfo(index, offset, length);
    }

    public boolean isKeepAlive () {
        return (id == -1);
    }

    public boolean isChoke() {
        return (id == 0);
    }

    public boolean isUnChoke() {
        return (id == 1);
    }

    public boolean isInterested() {
        return (id == 2);
    }

    public boolean isNotInterested() {
        return (id == 3);
    }

    public boolean isHave () {
        return (id == 4);
    }

    public boolean isBitfield () {
        return (id == 5);
    }

    public boolean isRequest () {
        return (id == 6);
    }

    public boolean isPiece () {
        return (id == 7);
    }

    public boolean isCancel () {
        return (id == 8);
    }

    public int returnHave () {
        if (id != 4)
            throw new IllegalStateException ("not a have msg");
        return ToolKit.bigEndianBytesToInt(payload, 0);
    }

    public BitSet returnBitfield() {
        if (id != 5)
            throw new IllegalStateException ("not a bitfield msg");
        return ToolKit.bytesToBitSet(payload);
    }

    public BlockInfo returnBlockInfo () {
        if (id != 6 && id != 7)
            throw new IllegalStateException ("not a piece or request msg");
        return block;
    }

    public byte[] returnPieceBytes () {
        return payload;
    }

    public String toString () {
        StringBuilder str = new StringBuilder ("(id=");
        str.append(id);
        str.append(')');
        if (block != null) {
            str.append(" (block=");
            str.append(block);
            str.append(')');
        }
        if (payload != null && payload.length > 0) {
            if (id == 4) {
                str.append(" (index=");
                str.append(ToolKit.bigEndianBytesToInt(payload, 0));
            } else {
                str.append(" (p");
                str.append(payload.length);
                str.append('=');
                str.append(Arrays.toString(payload));
            }
            str.append(')');
        }
        return new String (str);
    }

    public static Message readMsg (InputStream in) throws IOException {
        byte[] msgLength = new byte[4];
        if (in.read(msgLength) != 4)
            return null;
        int bytesLeft = ToolKit.bigEndianBytesToInt(msgLength, 0);
        if (bytesLeft == 0)
            return new Message (-1, null, null);
        int id = in.read();
        bytesLeft--;
        if (GlobalTorrentInfo.debugConn)
            System.out.println("msgheader: "+bytesLeft+':'+id);
        if (id < 0 || id > 9)
            return null;
        if (bytesLeft < 0)
            return null;
        byte[] block = null;
        if (id == 6) {
            if (bytesLeft != 12)
                return null;
            block = new byte [12];
            if (in.read(block) != 12)
                return null;
            bytesLeft = 0;
        }
        if (id == 7) {
            block = new byte[8];
            if (in.read(block) != 8)
                return null;
            bytesLeft -= 8;
            if (GlobalTorrentInfo.debugFile)
                System.out.println("piece: "+Arrays.toString(block)+bytesLeft);
        }
        if (bytesLeft > 16384)
            return null;
        byte[] payload = new byte[bytesLeft];
        int read = 0;
        while (read < bytesLeft) {
            int r = in.read(payload, read, bytesLeft - read);
            if (GlobalTorrentInfo.debugFile && id == 7)
                System.out.println("read: "+r);
            read += r;
        }
        return new Message (id, payload, block);
    }

    public static String readHandshake (InputStream in,
            TorrentFile tfile) throws IOException {
        if (in.read() != 19)
            return null;
        byte[] btProtocol = new byte[19];
        if (in.read(btProtocol) != 19)
            return null;
        String protocol = new String (btProtocol, "US-ASCII");
        if (!protocol.equals("BitTorrent protocol"))
            return null;
        byte[] filler = new byte[8];
        if (in.read(filler) != 8)
            return null;
        byte[] infoHash = new byte[20];
        if (in.read(infoHash) != 20)
            return null;
        if (!Arrays.equals(infoHash, tfile.info_hash_as_binary))
            return null;
        byte[] peerID = new byte[20];
        if (in.read(peerID) != 20)
            return null;
        return new String(peerID, "US-ASCII");
    }

}
